/*
    GNU GENERAL LICENSE
    Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2016 Lobo Evolution

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public
    License as published by the Free Software Foundation; either
    verion 3 of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General License for more details.

    You should have received a copy of the GNU General Public
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    

    Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it
 */
package org.lobobrowser.html.renderer;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.Iterator;

import org.lobobrowser.html.dombl.ModelNode;

/**
 * The Class BaseRCollection.
 */
public abstract class BaseRCollection extends BaseBoundableRenderable implements
RCollection {

    /**
     * Instantiates a new base r collection.
     *
     * @param container
     *            the container
     * @param modelNode
     *            the model node
     */
    public BaseRCollection(RenderableContainer container, ModelNode modelNode) {
        super(container, modelNode);
    }

    /*
     * (non-Javadoc)
     * @see org.lobobrowser.html.renderer.RCollection#focus()
     */
    @Override
    public void focus() {
        this.container.focus();
        // TODO: Plus local focus
    }

    /*
     * (non-Javadoc)
     * @see org.lobobrowser.html.renderer.RCollection#blur()
     */
    @Override
    public void blur() {
        RCollection parent = this.parent;
        if (parent != null) {
            parent.focus();
        } else {
            // TODO: Remove local focus
        }
    }

    /**
     * Updates bounds of all descendent's GUI components, based on root bounds.
     *
     * @param guiX
     *            the gui x
     * @param guiY
     *            the gui y
     */
    @Override
    public void updateWidgetBounds(int guiX, int guiY) {
        Iterator i = this.getRenderables();
        if (i != null) {
            while (i.hasNext()) {
                Object r = i.next();
                if (r instanceof RCollection) {
                    // RUIControl is a RCollection too.
                    RCollection rc = (RCollection) r;
                    rc.updateWidgetBounds(guiX + rc.getX(), guiY + rc.getY());
                }
            }
        }
    }

    /**
     * Check start selection.
     *
     * @param bounds
     *            the bounds
     * @param selectionPoint
     *            the selection point
     * @return true, if successful
     */
    private boolean checkStartSelection(Rectangle bounds, Point selectionPoint) {
        if (bounds.y > selectionPoint.y) {
            return true;
        } else if ((selectionPoint.y >= bounds.y)
                && (selectionPoint.y < (bounds.y + bounds.height))
                && (bounds.x > selectionPoint.x)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Check end selection.
     *
     * @param bounds
     *            the bounds
     * @param selectionPoint
     *            the selection point
     * @return true, if successful
     */
    private boolean checkEndSelection(Rectangle bounds, Point selectionPoint) {
        if (bounds.y > selectionPoint.y) {
            return true;
        } else if ((selectionPoint.y >= bounds.y)
                && (selectionPoint.y < (bounds.y + bounds.height))
                && (selectionPoint.x < bounds.x)) {
            return true;
        } else {
            return false;
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * org.lobobrowser.html.renderer.BoundableRenderable#paintSelection(java.awt
     * .Graphics, boolean, org.lobobrowser.html.renderer.RenderableSpot,
     * org.lobobrowser.html.renderer.RenderableSpot)
     */
    @Override
    public boolean paintSelection(Graphics g, boolean inSelection,
            RenderableSpot startPoint, RenderableSpot endPoint) {
        // TODO: Does this work with renderables that are absolutely positioned?
        Point checkPoint1 = null;
        Point checkPoint2 = null;
        if (!inSelection) {
            boolean isStart = startPoint.getRenderable() == this;
            boolean isEnd = endPoint.getRenderable() == this;
            if (isStart && isEnd) {
                checkPoint1 = startPoint.getPoint();
                checkPoint2 = endPoint.getPoint();
            } else if (isStart) {
                checkPoint1 = startPoint.getPoint();
            } else if (isEnd) {
                checkPoint1 = endPoint.getPoint();
            }
        } else {
            if (startPoint.getRenderable() == this) {
                checkPoint1 = startPoint.getPoint();
            } else if (endPoint.getRenderable() == this) {
                checkPoint1 = endPoint.getPoint();
            }
        }
        Iterator i = this.getRenderables();
        if (i != null) {
            while (i.hasNext()) {
                Object robj = i.next();
                if (robj instanceof BoundableRenderable) {
                    BoundableRenderable renderable = (BoundableRenderable) robj;
                    Rectangle bounds = renderable.getBounds();
                    if (!inSelection) {
                        if ((checkPoint1 != null)
                                && this.checkStartSelection(bounds, checkPoint1)) {
                            if (checkPoint2 != null) {
                                checkPoint1 = checkPoint2;
                                checkPoint2 = null;
                            } else {
                                checkPoint1 = null;
                            }
                            inSelection = true;
                        } else if ((checkPoint2 != null)
                                && this.checkStartSelection(bounds, checkPoint2)) {
                            checkPoint1 = null;
                            checkPoint2 = null;
                            inSelection = true;
                        }
                    } else if (inSelection && (checkPoint1 != null)
                            && this.checkEndSelection(bounds, checkPoint1)) {
                        return false;
                    }
                    int offsetX = bounds.x;
                    int offsetY = bounds.y;
                    g.translate(offsetX, offsetY);
                    try {
                        boolean newInSelection = renderable.paintSelection(g,
                                inSelection, startPoint, endPoint);
                        if (inSelection && !newInSelection) {
                            return false;
                        }
                        inSelection = newInSelection;
                    } finally {
                        g.translate(-offsetX, -offsetY);
                    }
                }
            }
        }
        if (inSelection && (checkPoint1 != null)) {
            return false;
        } else if (!inSelection
                && ((checkPoint1 != null) || (checkPoint2 != null))
                && !((checkPoint1 != null) && (checkPoint2 != null))) {
            // Has to have started not being in selection,
            // but we must start selecting without having
            // selected anything in the block then.
            return true;
        }
        return inSelection;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.lobobrowser.html.renderer.BoundableRenderable#extractSelectionText(java
     * .lang.StringBuffer, boolean, org.lobobrowser.html.renderer.RenderableSpot,
     * org.lobobrowser.html.renderer.RenderableSpot)
     */
    @Override
    public boolean extractSelectionText(StringBuffer buffer,
            boolean inSelection, RenderableSpot startPoint,
            RenderableSpot endPoint) {
        Point checkPoint1 = null;
        Point checkPoint2 = null;
        if (!inSelection) {
            boolean isStart = startPoint.getRenderable() == this;
            boolean isEnd = endPoint.getRenderable() == this;
            if (isStart && isEnd) {
                checkPoint1 = startPoint.getPoint();
                checkPoint2 = endPoint.getPoint();
            } else if (isStart) {
                checkPoint1 = startPoint.getPoint();
            } else if (isEnd) {
                checkPoint1 = endPoint.getPoint();
            }
        } else {
            if (startPoint.getRenderable() == this) {
                checkPoint1 = startPoint.getPoint();
            } else if (endPoint.getRenderable() == this) {
                checkPoint1 = endPoint.getPoint();
            }
        }
        Iterator i = this.getRenderables();
        if (i != null) {
            while (i.hasNext()) {
                Object robj = i.next();
                if (robj instanceof BoundableRenderable) {
                    BoundableRenderable renderable = (BoundableRenderable) robj;
                    if (!inSelection) {
                        Rectangle bounds = renderable.getBounds();
                        if ((checkPoint1 != null)
                                && this.checkStartSelection(bounds, checkPoint1)) {
                            if (checkPoint2 != null) {
                                checkPoint1 = checkPoint2;
                                checkPoint2 = null;
                            } else {
                                checkPoint1 = null;
                            }
                            inSelection = true;
                        } else if ((checkPoint2 != null)
                                && this.checkStartSelection(bounds, checkPoint2)) {
                            checkPoint1 = null;
                            checkPoint2 = null;
                            inSelection = true;
                        }
                    } else if (inSelection
                            && (checkPoint1 != null)
                            && this.checkEndSelection(renderable.getBounds(),
                                    checkPoint1)) {
                        return false;
                    }
                    boolean newInSelection = renderable.extractSelectionText(
                            buffer, inSelection, startPoint, endPoint);
                    if (inSelection && !newInSelection) {
                        return false;
                    }
                    inSelection = newInSelection;
                }
            }
        }
        if (inSelection && (checkPoint1 != null)) {
            return false;
        } else if (!inSelection
                && ((checkPoint1 != null) || (checkPoint2 != null))
                && !((checkPoint1 != null) && (checkPoint2 != null))) {
            // Has to have started not being in selection,
            // but we must start selecting without having
            // selected anything in the block then.
            return true;
        }
        return inSelection;
    }

    /*
     * (non-Javadoc)
     * @see org.lobobrowser.html.renderer.RCollection#invalidateLayoutDeep()
     */
    @Override
    public void invalidateLayoutDeep() {
        // TODO: May be pretty inefficient in RLine's
        // if it's true that non-layable components
        // are not in RLine's anymore.
        this.invalidateLayoutLocal();
        Iterator renderables = this.getRenderables();
        if (renderables != null) {
            while (renderables.hasNext()) {
                Object r = renderables.next();
                if (r instanceof RCollection) {
                    ((RCollection) r).invalidateLayoutDeep();
                }
            }
        }
    }

    /** The renderable with mouse. */
    private BoundableRenderable renderableWithMouse = null;

    /*
     * (non-Javadoc)
     * @see
     * org.lobobrowser.html.renderer.BaseBoundableRenderable#onMouseMoved(java.awt
     * .event.MouseEvent, int, int, boolean, org.lobobrowser.html.dombl.ModelNode)
     */
    @Override
    public void onMouseMoved(MouseEvent event, int x, int y,
            boolean triggerEvent, ModelNode limit) {
        super.onMouseMoved(event, x, y, triggerEvent, limit);
        BoundableRenderable oldRenderable = this.renderableWithMouse;
        Renderable r = this.getRenderable(x, y);
        BoundableRenderable newRenderable = r instanceof BoundableRenderable ? (BoundableRenderable) r
                : null;
        ModelNode newLimit;
        if (this.isContainedByNode()) {
            newLimit = this.modelNode;
        } else {
            newLimit = limit;
        }
        boolean changed = oldRenderable != newRenderable;
        if (changed) {
            if (oldRenderable != null) {
                oldRenderable.onMouseOut(event, x - oldRenderable.getX(), y
                        - oldRenderable.getY(), newLimit);
            }
            this.renderableWithMouse = newRenderable;
        }
        // Must recurse always
        if (newRenderable != null) {
            newRenderable.onMouseMoved(event, x - newRenderable.getX(), y
                    - newRenderable.getY(), changed, newLimit);
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * org.lobobrowser.html.renderer.BaseBoundableRenderable#onMouseOut(java.awt
     * .event.MouseEvent, int, int, org.lobobrowser.html.dombl.ModelNode)
     */
    @Override
    public void onMouseOut(MouseEvent event, int x, int y, ModelNode limit) {
        super.onMouseOut(event, x, y, limit);
        BoundableRenderable oldRenderable = this.renderableWithMouse;
        if (oldRenderable != null) {
            this.renderableWithMouse = null;
            ModelNode newLimit;
            if (this.isContainedByNode()) {
                newLimit = this.modelNode;
            } else {
                newLimit = limit;
            }
            oldRenderable.onMouseOut(event, x - oldRenderable.getX(), y
                    - oldRenderable.getY(), newLimit);
        }
    }

    /**
     * Gets the renderable.
     *
     * @param x
     *            the x
     * @param y
     *            the y
     * @return the renderable
     */
    public BoundableRenderable getRenderable(int x, int y) {
        Iterator i = this.getRenderables();
        if (i != null) {
            while (i.hasNext()) {
                Object r = i.next();
                if (r instanceof BoundableRenderable) {
                    BoundableRenderable br = (BoundableRenderable) r;

                    if (br instanceof RBlockViewport) {
                        return br;
                    }

                    int bx = br.getX();
                    int by = br.getY();
                    if ((y >= by) && (y < (by + br.getHeight())) && (x >= bx)
                            && (x < (bx + br.getWidth()))) {
                        return br;
                    }
                }
            }
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.lobobrowser.html.renderer.BoundableRenderable#onRightClick(java.awt.event
     * .MouseEvent, int, int)
     */
    @Override
    public boolean onRightClick(MouseEvent event, int x, int y) {
        BoundableRenderable br = this.getRenderable(x, y);
        if (br == null) {
            return HtmlController.getInstance().onContextMenu(this.modelNode,
                    event, x, y);
        } else {
            return br.onRightClick(event, x - br.getX(), y - br.getY());
        }
    }
}
